﻿namespace Sidvall.Security
{
    public class PropertyAccessLevelEvaluator : IPropertyAccessLevelEvaluator
    {
        #region Public Members

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public EntityItemCollection EntityItems { get; set; }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public PropertyItemCollection PropertyItems { get; set; }

        #region BypassCanAccessCheck

        private bool _BypassCanAccessCheck = false;
        protected virtual bool BypassCanAccessCheck
        {
            get
            {
                return _BypassCanAccessCheck;
            }
            set
            {
                _BypassCanAccessCheck = value;
            }
        }

        #endregion
        #region BypassEntityAccessCheck

        private bool _BypassEntityAccessCheck = false;
        protected virtual bool BypassEntityAccessCheck
        {
            get
            {
                return _BypassEntityAccessCheck;
            }
            set
            {
                _BypassEntityAccessCheck = value;
            }
        }

        #endregion
        #region BypassPropertyAccessCheck

        private bool _BypassPropertyAccessCheck = false;
        protected virtual bool BypassPropertyAccessCheck
        {
            get
            {
                return _BypassPropertyAccessCheck;
            }
            set
            {
                _BypassPropertyAccessCheck = value;
            }
        }

        #endregion

        #region CanAccess

        public virtual bool CanAccess(IPropertyAccessLevelProvider accessLevelProvider, bool isKeyProperty, bool propertyIsInRelation, bool isRelationProperty,
            Sidvall.Data.DataType propertyDataType, string propertyName, string entityName)
        {
            int accessLevel, itemAccessLevel;
            bool useReadValue;

            if (this.BypassCanAccessCheck)
                return _CanAccessDefaultValue;
            if (string.IsNullOrWhiteSpace(propertyName))
                return !_CanAccessDefaultValue;
            if (accessLevelProvider != null)
                accessLevel = accessLevelProvider.GetAccessLevel(propertyName);
            else
                accessLevel = 0;
            switch (_PropertyOperationType)
            {
                case PropertyOperationType.Write:
                    useReadValue = false;
                    break;
                default:
                    useReadValue = true;
                    break;
            }

            // Check propertyName
            if (!this.BypassPropertyAccessCheck)
            {
                var propertyExists = false;
                var propertyItem = FindPropertyItem(propertyName, accessLevel, useReadValue, isKeyProperty, propertyIsInRelation, isRelationProperty, propertyDataType);
                if (propertyItem == null)
                {
                    if (PropertyExists(propertyName, accessLevel, useReadValue, isKeyProperty, propertyIsInRelation, isRelationProperty, propertyDataType))
                    {
                        propertyExists = true;
                    }
                    else
                    {
                        var propertyWildcard = GetPropertyWildcard(propertyName, accessLevel, useReadValue, isKeyProperty, propertyIsInRelation, isRelationProperty, propertyDataType);
                        if (!string.IsNullOrWhiteSpace(propertyWildcard))
                        {
                            propertyItem = FindPropertyItem(propertyWildcard, accessLevel, useReadValue, isKeyProperty, propertyIsInRelation, isRelationProperty, propertyDataType);
                            if (propertyItem == null)
                            {
                                if (PropertyExists(propertyWildcard, accessLevel, useReadValue, isKeyProperty, propertyIsInRelation, isRelationProperty, propertyDataType))
                                    propertyExists = true;
                            }
                        }
                    }
                }
                if (propertyItem != null)
                {
                    if (useReadValue)
                        itemAccessLevel = propertyItem.ReadAccessLevel;
                    else
                        itemAccessLevel = propertyItem.WriteAccessLevel;
                    if (itemAccessLevel >= accessLevel)
                        return _CanAccessDefaultValue;
                    propertyExists = true;
                }
                if (propertyExists)
                    return !_CanAccessDefaultValue;
            }

            // Check entityName
            if (!this.BypassEntityAccessCheck)
            {
                if (!string.IsNullOrWhiteSpace(entityName))
                {
                    var entityExists = false;
                    var entityItem = FindEntityItem(entityName, accessLevel, useReadValue, isKeyProperty, propertyIsInRelation, isRelationProperty, propertyDataType);
                    if (entityItem == null)
                    {
                        if (EntityExists(entityName, accessLevel, useReadValue, isKeyProperty, propertyIsInRelation, isRelationProperty, propertyDataType))
                            entityExists = true;
                    }
                    if (entityItem != null)
                    {
                        if (useReadValue)
                            itemAccessLevel = entityItem.ReadAccessLevel;
                        else
                            itemAccessLevel = entityItem.WriteAccessLevel;
                        if (itemAccessLevel >= accessLevel)
                            return _CanAccessDefaultValue;
                        entityExists = true;
                    }
                    if (entityExists)
                        return !_CanAccessDefaultValue;
                }
            }

            // Use default value for missing property
            itemAccessLevel = GetDefaultAccessLevel(propertyName, entityName, accessLevel, useReadValue, isKeyProperty, propertyIsInRelation, isRelationProperty, propertyDataType);
            if (itemAccessLevel >= accessLevel)
                return _CanAccessDefaultValue;
            return !_CanAccessDefaultValue;
        }

        #endregion
        #region SetPropertyOptions

        public void SetPropertyOptions(PropertyOperationType propertyOperationType, bool canAccessDefaultValue)
        {
            _PropertyOperationType = propertyOperationType;
            _CanAccessDefaultValue = canAccessDefaultValue;
        }

        #endregion

        // Protected
        #region EntityExists

        protected virtual bool EntityExists(string entityName, int accessLevel, bool useReadValue, bool isKeyProperty, bool propertyIsInRelation, bool isRelationProperty,
            Sidvall.Data.DataType propertyDataType)
        {
            return true;
        }

        #endregion
        #region FindEntityItem

        protected virtual EntityItem FindEntityItem(string entityName, int accessLevel, bool useReadValue, bool isKeyProperty, bool propertyIsInRelation, bool isRelationProperty,
            Sidvall.Data.DataType propertyDataType)
        {
            if (this.EntityItems == null)
                return null;
            return this.EntityItems.FindItem(entityName);
        }

        #endregion
        #region FindPropertyItem

        protected virtual PropertyItem FindPropertyItem(string propertyName, int accessLevel, bool useReadValue, bool isKeyProperty, bool propertyIsInRelation, bool isRelationProperty,
            Sidvall.Data.DataType propertyDataType)
        {
            if (this.PropertyItems == null)
                return null;
            return this.PropertyItems.FindItem(propertyName);
        }

        #endregion
        #region GetDefaultAccessLevel

        protected virtual int GetDefaultAccessLevel(string propertyName, string entityName,
            int accessLevel, bool useReadValue, bool isKeyProperty, bool propertyIsInRelation, bool isRelationProperty, Sidvall.Data.DataType propertyDataType)
        {
            return -1;
        }

        #endregion
        #region GetPropertyWildcard

        protected virtual string GetPropertyWildcard(string propertyName, int accessLevel, bool useReadValue, bool isKeyProperty, bool propertyIsInRelation, bool isRelationProperty,
            Sidvall.Data.DataType propertyDataType)
        {
            if (string.IsNullOrWhiteSpace(propertyName))
                return null;
            var pos = propertyName.LastIndexOf('.');
            if (pos > 0)
            {
                if (isRelationProperty)
                    return propertyName.Substring(0, pos + 1) + "*relation*";
                else
                    return propertyName.Substring(0, pos + 1) + "*property*";
            }
            else
            {
                return null;
            }
        }

        #endregion
        #region PropertyExists

        protected virtual bool PropertyExists(string propertyName, int accessLevel, bool useReadValue, bool isKeyProperty, bool propertyIsInRelation, bool isRelationProperty,
            Sidvall.Data.DataType propertyDataType)
        {
            return true;
        }

        #endregion

        #endregion
        #region Private Members

        private PropertyOperationType _PropertyOperationType;
        private bool _CanAccessDefaultValue;

        #endregion
        #region Constructors

        public PropertyAccessLevelEvaluator()
            : this(PropertyOperationType.Read, true)
        {
        }
        public PropertyAccessLevelEvaluator(PropertyOperationType propertyOperationType, bool canAccessDefaultValue)
        {
            this.EntityItems = new EntityItemCollection();
            this.PropertyItems = new PropertyItemCollection();
            _PropertyOperationType = propertyOperationType;
            _CanAccessDefaultValue = canAccessDefaultValue;
        }

        #endregion
    }
}
